头像框控件
头像框控件
- 自定义组合控件,头像框和头像成一定比例,但有个问题,就是没有头像框的时候,会多出来一点间距,这个调整 ui 工作量就大了
- 头像框控件固定,间距也固定,头像框就盖在头像上面,通过
clipChildren来控制绘制到父控件中
Mashi 头像框控件
- 支持 webp 静态图
- 支持 svga 动态图
// 头像:头像框 150:198
open class AvatarBoxView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val ivAvatar: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar) }
private val ivAvatarBoxStatic: BaseImageView by lazy { findViewById<BaseImageView>(R.id.iv_avatar_box_static) }
private val ivAvatarBoxSVGA: CommonSVGAView by lazy { findViewById<CommonSVGAView>(R.id.iv_avatar_box_svga) }
private var goodsDetail: StoreGoodsDetail? = null
private var tag = "default"
private var clipCount = 1
companion object {
private const val TAG = StoreConstants.TAG
}
init {
LayoutInflater.from(context).inflate(R.layout.view_avatar_layout, this, true)
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AvatarBoxView)
try {
clipCount = typedArray?.getInt(R.styleable.AvatarBoxView_clip_count, 1) ?: 1
} finally {
typedArray?.recycle()
}
}
fun getAvatarBoxSVGA(): SVGAImageView? {
if (goodsDetail == null || goodsDetail?.isStaticPreview() != false) {
return null
}
return ivAvatarBoxSVGA
}
fun isAvatarBoxSVGAShow(): Boolean {
val avatarBoxSVGA = getAvatarBoxSVGA()
return avatarBoxSVGA != null && avatarBoxSVGA.drawable != null
}
fun showAvatarBoxSVGADirect() {
getAvatarBoxSVGA()?.startAnimation()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// LogUtils.d(TAG, "onMeasure widthSize=$widthSize, heightSize=$heightSize")
if (widthSize == 0 && heightSize == 0) {
// If there are no constraints on size, let AvatarView measure
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Now use the smallest of the measured dimensions for both dimensions
val minSize = measuredWidth.coerceAtMost(measuredHeight)
setMeasuredDimension(minSize, minSize)
// LogUtils.w(TAG, "onMeasure minSize=$minSize")
return
}
val size: Int
size = if (widthSize == 0 || heightSize == 0) {
// If one of the dimensions has no restriction on size, set both dimensions to be the
// on that does
widthSize.coerceAtLeast(heightSize)
// LogUtils.w(TAG, "onMeasure(widthSize == 0 || heightSize == 0) size=$size")
} else {
// Both dimensions have restrictions on size, set both dimensions to be the
// smallest of the two
widthSize.coerceAtMost(heightSize)
// LogUtils.w(TAG, "onMeasure(widthSize and heightSize != 0) size=$size")
}
val newMeasureSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY)
super.onMeasure(newMeasureSpec, newMeasureSpec)
}
override fun measureChildWithMargins(child: View?, parentWidthMeasureSpec: Int, widthUsed: Int, parentHeightMeasureSpec: Int, heightUsed: Int) {
val widthSize = MeasureSpec.getSize(parentWidthMeasureSpec)
val heightSize = MeasureSpec.getSize(parentHeightMeasureSpec)
if (child?.id == R.id.iv_avatar_box_static || child?.id == R.id.iv_avatar_box_svga) {
// LogUtils.d(TAG, "measureChildWithMargins(R.id.iv_avatar) widthSize=$widthSize, heightSize=$heightSize child=$child")
val size: Int
if (widthSize == 0 || heightSize == 0) {
// If one of the dimensions has no restriction on size, set both dimensions to be the
// on that does
size = widthSize.coerceAtLeast(heightSize)
// LogUtils.w(TAG, "measureChildWithMargins(widthSize == 0 || heightSize == 0) size=$size")
} else {
// Both dimensions have restrictions on size, set both dimensions to be the
// smallest of the two
size = widthSize.coerceAtMost(heightSize)
//LogUtils.w(TAG, "measureChildWithMargins(widthSize and heightSize != 0) size=$size")
}
val avatarSize = size * (198.0 / 150.0) // FIXME:ZFS 2020年03月03日20:07:53 改成可配置?
// LogUtils.w(TAG, "measureChildWithMargins final size avatarSize=$avatarSize(${avatarSize.toInt()})")
val newMeasureSpec = MeasureSpec.makeMeasureSpec(avatarSize.toInt(), MeasureSpec.EXACTLY)
super.measureChildWithMargins(child, newMeasureSpec, widthUsed, newMeasureSpec, heightUsed)
} else {
// LogUtils.d(TAG, "measureChildWithMargins widthSize=$widthSize, heightSize=$heightSize,widthUsed=$widthUsed, heightUsed=$heightUsed, child=$child")
super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed)
}
}
fun tag(tag: String): AvatarBoxView {
this.tag = tag
return this
}
fun showAvatarAndBox(user: User?): AvatarBoxView {
showAvatar(user).showAvatarBox(user?.avatarBox)
return this
}
fun showAvatar(user: User?): AvatarBoxView {
ivAvatar.visible().showAvatar(user?.avatar ?: "")
return this
}
fun showAvatarBox(storeGoodsDetail: StoreGoodsDetail?): AvatarBoxView {
this.goodsDetail = storeGoodsDetail
if (storeGoodsDetail?.previewPic == null) {
hideAvatarBox()
} else {
storeGoodsDetail.previewPic?.apply {
if (isStatic) {
// LogUtils.d(TAG, "${anchor("showAvatarBox")}静态头像框,picUrl=$picUrl")
// ivAvatarBoxStatic.visible().showAvatar(picUrl ?: "")
ivAvatarBoxStatic.setImageURI("")
ivAvatarBoxSVGA.gone()
ImageLoaderManager.createImageOptions(ivAvatarBoxStatic.visible(), getPreviewPicUrl(storeGoodsDetail)
?: "")
.setOnLoaderResultCallBack(object : OnLoaderResultCallBack {
override fun onSucc(animatable: Animatable?) {
}
override fun onFail(throwable: Throwable?) {
LogUtils.printStackTrace(throwable)
LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]静态头像框加载失败(${throwable?.message}),picUrl=$picUrl", true)
}
})
.show()
} else {
val pic = getPreviewPicUrl(storeGoodsDetail) ?: ""
ivAvatarBoxStatic.setImageURI("")
ivAvatarBoxSVGA.stop()
ivAvatarBoxSVGA.tag = pic
ivAvatarBoxSVGA.visible()
.loop()
.setCallback(object : CommonSVGAView.ParseCallback {
override fun onError() {
LogUtils.w(TAG, "${this@AvatarBoxView.anchor("showAvatarBox")}[$tag]动态SVGA头像框加载失败,picUrl=$picUrl", true)
}
})
.show(pic)
}
}
}
return this
}
// 隐藏头像框
fun hideAvatarBox() {
LogUtils.w(TAG, "${anchor("hideAvatarBox")}[$tag]隐藏头像框静态/SVGA,previewPic=${goodsDetail?.previewPic}", true)
ivAvatarBoxStatic.gone()
ivAvatarBoxSVGA.gone()
}
override fun dispatchDraw(canvas: Canvas?) {
clip(this)
super.dispatchDraw(canvas)
}
private fun clip(v: ViewGroup?) {
if (v == null || clipCount < 0) {
return
}
v.clipChildrenNot().clipPaddingNot()
// LogUtils.i(TAG, "${anchor("dispatchDraw")}[$tag] clipCount=$clipCount, parent_id=${v.id},parent clipChildren & clipToPadding = false,parent=$v")
while (clipCount >= 0) {
clipCount--
clip(v.parent as? ViewGroup)
}
}
}
糗百静态图头像框
在 ImageView 的 onDraw,通过 canvas translate 到父控件外,再通过 clipChildren 来实现
public class VHeadSimpleDrawee extends QBThemeImageView {
/**
* 大v图标的大小跟头像本身大小比例是 0.35 找iOS同学确认的,最好在初始化的时候写进去
*/
public static final float SCALE = 0.35f;
private Drawable mBottomDrawable = null;
private int drawableWidth;
private int drawableHeight;
private int defValue = 0;
private DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
public VHeadSimpleDrawee(Context context, GenericDraweeHierarchy hierarchy) {
super(context, hierarchy);
init(null);
}
public VHeadSimpleDrawee(Context context) {
super(context);
init(null);
}
public VHeadSimpleDrawee(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public VHeadSimpleDrawee(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
public VHeadSimpleDrawee(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
private void init(AttributeSet attributeSet) {
defValue = getContext().getResources().getDimensionPixelSize(R.dimen.qb_px_15);
TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.VHeadSimpleDrawee);
if (a != null) {
try {
drawableHeight = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_height, defValue);
drawableWidth = a.getDimensionPixelSize(R.styleable.VHeadSimpleDrawee_drawable_width, defValue);
} finally {
a.recycle();
}
}
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources()).build();
mDraweeHolder = DraweeHolder.create(hierarchy, getContext());
mDraweeHolder.getTopLevelDrawable().setCallback(this);
}
public void setDrawableSize(int width, int height) {
drawableHeight = height;
drawableWidth = width;
invalidate();
}
public void showVImage(List<TalentBean> talentBeans) {
showVImage((talentBeans != null && talentBeans.size() > 0) ? talentBeans.get(0) : null);
}
private void showVImage(TalentBean talent) {
if (talent != null) {
mBottomDrawable = getResources().getDrawable(getResFromTalent(talent));
} else {
mBottomDrawable = null;
}
invalidate();
}
public static int getResFromTalent(TalentBean talent) {
if (talent == null) {
return 0;
}
if (talent.cmd < 0) {
return UIHelper.isNightTheme() ? R.drawable.im_ic_certification_night : R.drawable.im_ic_certification;
} else {
return UIHelper.isNightTheme() ?R.drawable.icon_head_v_night : R.drawable.icon_head_v;
}
}
public void photoFrame(String photoFrame) {
DraweeController controller;
Uri uri = Uri.EMPTY;
try {
if (!TextUtils.isEmpty(photoFrame)) {
uri = Uri.parse(photoFrame);
}
} catch (Exception e) {
e.printStackTrace();
}
if (mDraweeHolder != null) {
controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build();
mDraweeHolder.setController(controller);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
ViewGroup parent = (ViewGroup) getParent();
if (parent != null) {
parent.setClipChildren(false);
parent.setClipToPadding(false);
}
if (getMeasuredWidth() > 0 && mDraweeHolder != null && mDraweeHolder.getTopLevelDrawable() != null) {
int width = getMeasuredWidth();
int photoFrameSize = (int) (width * 1.4f);
canvas.save();
canvas.translate(-(photoFrameSize - width)/2, -(photoFrameSize - width)/2);
Drawable drawable = mDraweeHolder.getTopLevelDrawable();
drawable.setBounds(new Rect(0, 0, photoFrameSize, photoFrameSize));
drawable.draw(canvas);
canvas.restore();
}
int imgWidth = getMeasuredWidth();
int imgHeight = getMeasuredHeight();
if (imgHeight > 0 && imgWidth > 0) {
drawableHeight = (int) (imgHeight * (SCALE));
drawableWidth = (int) (imgWidth * (SCALE));
}
if (mBottomDrawable != null) {
int width = getMeasuredWidth();
int height = getMeasuredHeight();
int left = width - drawableWidth;
int top = height - drawableHeight;
mBottomDrawable.setColorFilter(getColorFilter());
mBottomDrawable.setBounds(
left,
top,
width,
height);
mBottomDrawable.draw(canvas);
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mDraweeHolder != null) {
mDraweeHolder.onDetach();
}
}
@Override
public void onStartTemporaryDetach() {
super.onStartTemporaryDetach();
if (mDraweeHolder != null) {
mDraweeHolder.onDetach();
}
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
if (mDraweeHolder != null) {
mDraweeHolder.onAttach();
}
}
@Override
public void onFinishTemporaryDetach() {
super.onFinishTemporaryDetach();
if (mDraweeHolder != null) {
mDraweeHolder.onAttach();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable dr) {
if (mDraweeHolder != null && dr == mDraweeHolder.getTopLevelDrawable()) {
return true;
}
return super.verifyDrawable(dr);
}
}